package app

import (
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
	"github.com/AsynkronIT/protoactor-go/remote"

	nactor "nggs/actor"
	ndebug "nggs/debug"
	nexport "nggs/export"
	nlog "nggs/log"
	nservice "nggs/service"
	nutil "nggs/util"
)

const ActorName = "app"

type App struct {
	*nactor.Actor

	startedWg sync.WaitGroup
	stoppedWg sync.WaitGroup

	cfg    nexport.IAppConfig
	logger nlog.ILogger

	pprofAddr string

	services []nexport.IService
}

func New(cfg nexport.IAppConfig, logger nlog.ILogger) (app *App) {
	app = &App{
		cfg: cfg,
	}

	app.Actor = nactor.New(
		nactor.WithLogger(logger),
		nactor.WithStartedWaitGroup(&app.startedWg),
		nactor.WithStoppedWaitGroup(&app.stoppedWg),
		nactor.WithOnStarted(app.onStarted),
		nactor.WithOnStopped(app.onStopped),
		nactor.WithOnActorTerminate(app.onActorTerminated),
	)

	app.cfg.EachServiceConfig(func(index int, serviceConfig nexport.IServiceConfigInApp) (continued bool) {
		service, err := nservice.FM.Produce(serviceConfig.GetName())
		if err != nil {
			log.Panicf("produce servcie[%s][%d] fail, %s", serviceConfig.GetName(), serviceConfig.GetID(), err)
		}
		err = service.Init(app.cfg.Clone().(nexport.IAppConfig), serviceConfig.GetID(), &app.startedWg, &app.stoppedWg, app.cfg)
		if err != nil {
			log.Panicf("init %s[%d] fail, %s", serviceConfig.GetName(), serviceConfig.GetID(), err)
		}
		app.services = append(app.services, service)
		return true
	})

	return
}

func (a *App) GetID() nexport.ServiceID {
	return a.cfg.GetID()
}

func (a *App) GetConfig() nexport.IAppConfig {
	return a.cfg
}

func (a *App) Run() (err error) {
	if a.cfg.GetPprofIP() != "" {
		// 开启pprof server
		a.pprofAddr, err = ndebug.StartPprofServer(a.cfg.GetPprofIP(), a.cfg.GetPprofPort())
		if err != nil {
			log.Panicf("start pprof server fail, ip=%s, port=%d, %s", a.cfg.GetPprofIP(), a.cfg.GetPprofPort(), err)
		}
		defer ndebug.StopPprofServer()
		a.Info("start pprof server in %s", a.pprofAddr)
	}

	if a.cfg.GetEndpointIP() != "" {
		remote.Start(fmt.Sprintf("%s:%d", a.cfg.GetEndpointIP(), a.cfg.GetEndpointPort()))
		a.Info("start remote in %s", actor.ProcessRegistry.Address)
	}

	err = a.Start(nil, fmt.Sprintf("%s-%d", ActorName, a.cfg.GetID()))
	if err != nil {
		log.Panicf("start app fail, id=%d, %s", a.cfg.GetID(), err)
	}

	a.WaitForStarted()

	a.Info("running")

	// 启动接受ctrl+c命令的协程
	go func() {
		nutil.WaitExitSignal()

		a.Info("receive exit signal")

		for i := len(a.services) - 1; i >= 0; i-- {
			service := a.services[i]
			service.Stop()
			if a.cfg.GetStopServiceIntervalMSec() > 0 {
				time.Sleep(a.cfg.GetStopServiceIntervalMSec())
			}
		}

		if a.cfg.GetExitWaitSec() > 0 {
			time.Sleep(a.cfg.GetExitWaitSec())
		}

		a.Stop()
	}()

	a.WaitForStopped()

	return
}

func (a *App) onStarted(ctx actor.Context) {
	a.Info("start")

	for i, service := range a.services {
		serviceConfig, ok := a.cfg.GetServiceConfigByIndex(i)
		if !ok {
			a.Error("start %s fail, service config not found, index=%d", i)
			a.Stop()
			return
		}

		serviceName := fmt.Sprintf("%s-%d", serviceConfig.GetName(), serviceConfig.GetID())
		err := service.Run(ctx, a.pprofAddr)
		if err != nil {
			a.Error("start %s fail, %v", serviceName, err)
			a.Stop()
			return
		}

		a.Info("start %s success", serviceName)

		if a.cfg.GetStartServiceIntervalMSec() > 0 {
			time.Sleep(a.cfg.GetStartServiceIntervalMSec())
		}
	}
}

func (a *App) onStopped(ctx actor.Context) {
	a.Info("stopped")
}

func (a *App) onActorTerminated(who *actor.PID, ctx actor.Context) {
	a.Info("%s terminated", who.String())
}
